package cn.edu.hit.kg.entity;

//import org.neo4j.driver.v1.types.Node;

import cn.edu.hit.CacheManager;
import cn.edu.hit.CachedObjectPool;
import cn.edu.hit.ICachedObjectPool;
import cn.edu.hit.kg.data.Node;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

public class FeatureGroup extends BaseNode implements ISignalMatch {
    public FeatureGroup(Node node) {
        super(node);
    }

    @Override
    public boolean matchSignal(List<Signal> signal, long timestamp, String unitId) {
        if (lastTimestamp.get(unitId, 0L) == timestamp) {
            return matched;
        }
        lastTimestamp.push(unitId, timestamp);
        outMatches.clear();
        outSubIds.clear();
        outUnkonownIds.clear();

//        try (NeoDriver driver = new NeoDriver()){
        List<ISignalMatch> nodes = edgeCache.get(this.getId()).stream()
                .map(id -> (ISignalMatch) nodeCache.get(id))
                .collect(Collectors.toList());
//            List<ISignalMatch> nodes = driver.query("start n=node($gid) match (n)-[]->(m) return m ", Collections.singletonMap("gid",this.getId()))
//                    .list(record -> ISignalMatch.fromNode(record.get("m").asNode()));

        //System.out.println(this.getId()+":" + nodes.stream().map(node->node.getId()).collect(Collectors.toList()));

//        if (this.getId() == 2664) {
//            System.out.println(this.channelIds);
//        }

        if (this.get("combineType", stringTransfer).equalsIgnoreCase("and")) {
            unknown = nodes.stream().allMatch(x -> x.matchSignal(signal, timestamp, unitId) || x.isUnKnown());
            if (!unknown) {
                matched = false;
            } else {
                matched = nodes.stream()
                        .peek(iSignalMatch -> {
                            if (iSignalMatch.getOutIds().size() != 0) {
                                outMatches.add(iSignalMatch);
                                outMatches.addAll(iSignalMatch.getOutMatches());
                            }
                            Set<String> channelIds;
                            if (this.channelIds.has(unitId)) {
                                channelIds = this.channelIds.get(unitId);
                            } else {
                                channelIds = new HashSet<>();
                            }
                            channelIds.addAll(iSignalMatch.getChannelIds(unitId));
                            this.channelIds.push(unitId, Sets.newHashSet(channelIds));
                        })
                        .allMatch(x -> x.matchSignal(signal, timestamp, unitId));
            }
        } else if (this.get("combineType", stringTransfer).equalsIgnoreCase("or")) {
            List<Boolean> ml = nodes.stream()
                    .peek(iSignalMatch -> {
                        if (iSignalMatch.getOutIds().size() != 0) {
                            outMatches.add(iSignalMatch);
                            outMatches.addAll(iSignalMatch.getOutMatches());
                        }
                    })
                    .map(x -> x.matchSignal(signal, timestamp, unitId))
                    .collect(Collectors.toList());

            nodes.stream().forEach(iSignalMatch -> {
                if (iSignalMatch.isMatched()) {
                    if (iSignalMatch.getChannelIds(unitId) == null) {
                        System.err.println(iSignalMatch);
                    } else {
                        if (!this.channelIds.has(unitId)) {
                            this.channelIds.push(unitId, new HashSet<>());
                        }
                        HashSet<String> featureSignalSet = this.channelIds.get(unitId);
                        featureSignalSet.addAll(iSignalMatch.getChannelIds(unitId));
                        this.channelIds.push(unitId, featureSignalSet);
//                            this.channelIds.get(unitId).addAll(iSignalMatch.getChannelIds(unitId));
                    }
                }
            });
            matched = ml.parallelStream().anyMatch(Boolean::booleanValue);
            if (!matched) {
                unknown = nodes.stream().anyMatch(x -> x.isMatched() || x.isUnKnown());
            }
        } else if (this.get("combineType", stringTransfer).equalsIgnoreCase("not")) {//not all
            unknown = nodes.stream().allMatch(x -> (!x.matchSignal(signal, timestamp, unitId)) || x.isUnKnown());
            if (!unknown) {
                matched = false;
            } else {
                matched = nodes.stream().allMatch(x -> !x.matchSignal(signal, timestamp, unitId));
            }

            // TODO 关于 NOT 分支的测点检测比较复杂，后置处理，理论上应在各级加入一个导致结果为false的测点的集合
        }

//            System.out.println(this.getId()+","+this.get("combineType", Value::asString)+":"+matched+","+unknown);

        outSubIds = nodes.stream().filter(ISignalMatch::isMatched)
                .flatMap(iSignalMatch -> iSignalMatch.getSubMatched().stream())
                .collect(Collectors.toSet());
        nodes.stream().filter(ISignalMatch::isMatched).forEach(iSignalMatch -> outSubIds.add(iSignalMatch.getId()));

        if (!matched && unknown) {
            outUnkonownIds = nodes.stream().filter(onlyUnknown)
                    .flatMap(iSignalMatch -> iSignalMatch.getSubUnknown().stream())
                    .collect(Collectors.toSet());
            //System.out.println(outUnkonownIds);
            nodes.stream().filter(onlyUnknown).forEach(iSignalMatch -> outUnkonownIds.add(iSignalMatch.getId()));
            //System.out.println(outUnkonownIds);
        }
//        }
        return matched;
    }

    @Override
    public MatchResultType matchByConclusions(Map<Long, Boolean> conclusions) {
        List<ISignalMatch> nodes = edgeCache.get(this.getId()).stream()
                .map(id -> (ISignalMatch) nodeCache.get(id))
                .collect(Collectors.toList());

        if (this.get("combineType", stringTransfer).equalsIgnoreCase("and")) {
            return nodes.stream().allMatch(match->match.matchByConclusions(conclusions)==MatchResultType.TRUE)?MatchResultType.TRUE:MatchResultType.FALSE;
        }else if (this.get("combineType", stringTransfer).equalsIgnoreCase("or")) {
            return nodes.stream().anyMatch(match->match.matchByConclusions(conclusions)==MatchResultType.TRUE)?MatchResultType.TRUE:MatchResultType.FALSE;
        }else if (this.get("combineType", stringTransfer).equalsIgnoreCase("not")) {
            return nodes.stream().noneMatch(match->match.matchByConclusions(conclusions)==MatchResultType.TRUE)?MatchResultType.TRUE:MatchResultType.FALSE;
        }
        return MatchResultType.UNKNOWN;
    }

    @Override
    public MatchTrack trackByConclusions(Map<Long, Boolean> conclusions) {
        MatchTrack track = new MatchTrack();
        track.node = this;
        List<ISignalMatch> nodes = edgeCache.get(this.getId()).stream()
                .map(id -> (ISignalMatch) nodeCache.get(id))
                .collect(Collectors.toList());
        boolean stopOnPredicateMatches,shortCircuitResult;
        boolean isStop = false;

        String combineType = this.getString("combineType");
        if (combineType.equalsIgnoreCase("and")){
            stopOnPredicateMatches = shortCircuitResult = false;
        }else if (combineType.equalsIgnoreCase("not")){
            stopOnPredicateMatches = true;
            shortCircuitResult = false;
        }else {
            stopOnPredicateMatches = shortCircuitResult = true;
        }

        track.result = shortCircuitResult? MatchResultType.FALSE:MatchResultType.TRUE;
//        for (ISignalMatch signalMatch : nodes) {
//            MatchTrack subTrack;
//            if (isStop){
//                subTrack = new MatchTrack();
//                subTrack.node = (BaseNode) signalMatch;
//                subTrack.result = MatchResultType.SKIP;
//            }else {
//                subTrack = signalMatch.trackByConclusions(conclusions);
//            }
//
//            track.children.add(subTrack);
//            if (!isStop && (subTrack.result==MatchResultType.TRUE)==stopOnPredicateMatches){
//                isStop = true;
//                track.result = shortCircuitResult? MatchResultType.TRUE:MatchResultType.FALSE;
//            }
//        }
        for (ISignalMatch signalMatch : nodes) {
            MatchTrack subTrack = signalMatch.trackByConclusions(conclusions);
            track.children.add(subTrack);
            if (!isStop && (subTrack.result==MatchResultType.TRUE)==stopOnPredicateMatches){
                isStop = true;
                track.result = shortCircuitResult? MatchResultType.TRUE:MatchResultType.FALSE;
            }
        }

        return track;
    }

    @Override
    public List<Integer> getOutIds() {
        return Arrays.stream(this.get("outId", stringTransfer).split(","))
                .filter(x -> StringUtils.isNumericSpace(x) && StringUtils.isNotBlank(x))
                .mapToInt(Integer::parseInt)
                .filter(x -> x != 0)
                .boxed()
                .collect(Collectors.toList());
    }

    private boolean matched = false;
    private boolean unknown = false;
    private final CacheManager cacheManager = CacheManager.getInstance();
    private ICachedObjectPool<String, Long> lastTimestamp = cacheManager.createCachedObjectPool("FeatureGroup.lastTimestamp." + this.getId());

    @Override
    public boolean isMatched() {
        return matched;
    }

    @Override
    public boolean isUnKnown() {
        return unknown;
    }

    private Set<ISignalMatch> outMatches = new HashSet<>();
    private Set<Long> outSubIds = new HashSet<>();
    private Set<Long> outUnkonownIds = new HashSet<>();

    @Override
    public List<ISignalMatch> getOutMatches() {
        return ImmutableList.<ISignalMatch>builder().addAll(outMatches).build();
//        return new ArrayList<>(outMatches);
    }

    @Override
    public List<Long> getSubMatched() {
        return ImmutableList.<Long>builder().addAll(outSubIds).build();
//        return new ArrayList<>(outSubIds);
    }

    @Override
    public List<Long> getSubUnknown() {
        return ImmutableList.<Long>builder().addAll(outUnkonownIds).build();
//        return new ArrayList<>(outUnkonownIds);
    }

    private ICachedObjectPool<String, HashSet<String>> channelIds = cacheManager.createCachedObjectPool("FeatureGroup.channelIds." + this.getId());

    @Override
    public HashSet<String> getChannelIds(String unitId) {
        return channelIds.get(unitId, Sets.newHashSet());
    }

    @Override
    public Set<Feature> getFeatureSet() {
        return edgeCache.get(this.getId()).stream()
                .map(nodeCache::get)
                .filter(om -> !(om instanceof Fault) && om instanceof ISignalMatch)
                .flatMap(node->((ISignalMatch) node).getFeatureSet().stream())
                .collect(Collectors.toSet());
    }
}
